/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.streamline.services;

import com.vii.streamline.services.error.StreamLineException;

import java.io.*;
import java.nio.charset.Charset;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
 * This class encapsulate IO related services.
 */
public class IOServices {

    public static final int         FILE_READ_BUFFER = 8196;

    /**
     * Collects files from a given folder accepted by the given FileFilter
     * @param f source folder
     * @param list target collection where the File references will be put in
     * @param filter FileFilter reference
     */
    public static void collectFiles( File f, List<File> list, FileFilter filter ){
        if( f.exists() ){
            if( f.isDirectory() ){
                for( File file : f.listFiles( ) ){
                    collectFiles( file, list, filter );
                }
            }
            if( filter.accept( f ) )
                list.add( f );
        }
    }

    /**
     * Gets the given path as an InputStream
     * @param path path to the IO source
     * @return InputStream instance for the given path
     * @throws FileNotFoundException
     */
    public static InputStream getInputStream( String path ) throws FileNotFoundException {
        return new FileInputStream( path );
    }

    /**
     * Reads a byte array with a size limit from a givenInputStream
     * @param in inpustream instance
     * @param size size limit
     * @return byte array read from inputstream
     * @throws IOException
     */
    public static byte[] getStreamAsByteArray( InputStream in, int size ) throws IOException { int i;
        // Reserve space to read
        byte[] buff = new byte[size];

        DataInputStream dis = new DataInputStream( in );

        // Read in data
        dis.readFully(buff);

        // close stream
        dis.close();

        // return data
        return buff;
    }

    /**
     * Reads a byte array with a size limit from a givenInputStream
     * @param f file to be read
     * @param size size limit
     * @return byte array read from file
     * @throws IOException
     */
    public static byte[] getStreamAsByteArray( File f, int size ) throws IOException {
        return getStreamAsByteArray( new FileInputStream(f), size );
    }

    /**
     * Reads a byte array with a size limit from a givenInputStream
     * @param f file to be read
     * @return byte array read from file
     * @throws IOException
     */
    public static byte[] getStreamAsByteArray( File f ) throws IOException {
        return getStreamAsByteArray( f, (int)f.length() );
    }


    /**
     * Reads and retrieves every available bytes of an inputStream
     * @param in InputStream
     * @return String representation
     * @throws IOException
     */
    public static String getStreamAsString( InputStream in ) throws IOException { int i;
        StringBuilder sb = new StringBuilder();
        while( (i = in.read()) != -1 )
            sb.append( (char)i );
        return sb.toString();
    }

    /**
     * Write a string to a file
     * @param data String to be written to a file
     * @param filePath Full path to the file
     * @throws IOException
     */
    public static void writeToFile(String data, String filePath) throws IOException {
        File f = new File( filePath ); f.getParentFile().mkdirs();
        BufferedOutputStream bout = new BufferedOutputStream( new FileOutputStream( f ) );
        bout.write( data.getBytes() );
        bout.close();
    }

    /**
     * Write a string to a file
     * @param data Array of bytes to be written to a file
     * @param filePath Full path to the file
     * @throws IOException
     */
    public static void writeToFile(byte[] data, String filePath) throws IOException {
        BufferedOutputStream bout = new BufferedOutputStream( new FileOutputStream( filePath ) );
        bout.write( data );
        bout.close();
    }

    /**
     * Cascade deletes a file
     * @param f file to be deleted
     * @return logical value if it was successful
     */
    public static boolean delete(File f){
        if( !f.exists() ) return false;
        if( f.isDirectory() )
            for( File c : f.listFiles() )
                delete( c );
        return f.delete();
    }

    /**
     * Serialize an object into byte array
     * @param s object to be serialized
     * @return serialized object
     * @throws IOException
     */
    public static byte[] serialize( Serializable s ) throws IOException {
        if( s.getClass().equals( byte[].class ) )
            return (byte[])s;

        ByteArrayOutputStream bout = new ByteArrayOutputStream(  );
        ObjectOutputStream out = new ObjectOutputStream( bout );
        out.writeObject( s );
        out.close();
        return bout.toByteArray();
    }

    /**
     * Deserialize a byte array
     * @param b byte array to be deserialized
     * @param <T> Generic type of the serialized object
     * @return deserialized object
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static <T extends Serializable> T deSerialize( byte[] b ) throws IOException, ClassNotFoundException {
        ByteArrayInputStream bin = new ByteArrayInputStream( b );
        ObjectInputStream in = new ObjectInputStream( bin );
        T t = (T)in.readObject();
        in.close();
        return t;
    }

    public static byte[] gzipCompress( String s ) throws StreamLineException {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        try{
            GZIPOutputStream gout = new GZIPOutputStream( bout );
            gout.write( s.getBytes( Charset.forName("UTF-8") ) );
            gout.close();
            return bout.toByteArray();
        } catch( Exception e ){
            throw new StreamLineException( e.getMessage(), e );
        }
    }

    public static String gzipDecompress( byte[] a ) throws StreamLineException {
        ByteArrayInputStream bin = new ByteArrayInputStream( a );
        ByteArrayOutputStream bout = new ByteArrayOutputStream( );
        byte data[] = new byte[FILE_READ_BUFFER];

        try{
            GZIPInputStream gin = new GZIPInputStream( bin );
            int count;
            while((count = gin.read(data, 0, FILE_READ_BUFFER)) != -1) {
                bout.write(data, 0, count);
            }
            gin.close(); bout.close();

            String s = new String( bout.toByteArray(), Charset.forName("UTF-8") );
            return s;
        } catch( Exception e ){
            throw new StreamLineException( e.getMessage(), e );
        }
    }

    public static <T extends Serializable> byte[] compress( T t ) throws StreamLineException {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oout = null;
        try{
            oout = new ObjectOutputStream( new GZIPOutputStream( bout ) );
            oout.writeObject( t );
            oout.close();
            return bout.toByteArray();
        } catch( Exception e ){
            throw new StreamLineException( e.getMessage(), e );
        } finally {
            if( oout != null )
                try {
                    oout.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }

    public static <T extends Serializable> T decompress( byte[] a ) throws StreamLineException {
        ByteArrayInputStream bin = new ByteArrayInputStream( a );
        ObjectInputStream oin = null;
        try{
            oin = new ObjectInputStream( new GZIPInputStream( bin ) );
            T t = (T) oin.readObject( );
            return t;
        } catch( Exception e ){
            throw new StreamLineException( e.getMessage(), e );
        } finally {
            if( oin != null )
                try {
                    oin.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }

}
